﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: TypeMapping.cs
//
//  Description: The mapping of one persistent type.
// 
//--------------------------------------------------------------------------

using System;
using System.Reflection;
using System.Collections.Generic;
using Chili.Opf3.Relations;
using Chili.Opf3.Mapping.Providers;
using System.Runtime.CompilerServices;

namespace Chili.Opf3.Mapping
{
	/// <summary>
	/// Class that holds the mapping of one persistent object type with the storage.
	/// </summary>
	public sealed class TypeMapping
	{
		private static TypeAttributes _nonPublic = TypeAttributes.NotPublic;
		private Type _type;
		private PersistentAttribute _persistentAttribute;
		private MemberInfoCollection _members;
		private RelationInfoCollection _relations;
		private bool _isAnonymousType;

		/// <summary>
		/// Creates a new instance of the <see cref="TypeMapping">TypeMapping Class</see>.
		/// </summary>
		/// <param name="type">Type of object managed by this class.</param>
		/// <param name="persistentAttribute">Persistent attribute that decorates the class.</param>
		/// <param name="members">The list of the mapped members.</param>
		internal TypeMapping(Type type, PersistentAttribute persistentAttribute, MemberInfoCollection members)
		{
			if (persistentAttribute == null)
				throw new ArgumentNullException("persistentAttribute", "Can't create persistent mapping without persistent attribute.");

			_type = type;
			_persistentAttribute = persistentAttribute;
			_members = members;
			// Check if we have to deal with an anonymous type.
			_isAnonymousType = CheckIfAnonymousType(type);
		}

		/// <summary>
		/// Returns the persistent attribute associated with the object.
		/// </summary>
		public PersistentAttribute PersistentAttribute
		{
			get { return _persistentAttribute; }
		}

		/// <summary>
		/// Returns the type of the mapping's persistent object.
		/// </summary>
		public Type PersistentType
		{
			get { return _type; }
		}

		/// <summary>
		/// Gets whether the type is an anonymous type.
		/// </summary>
		public bool IsAnonymousType
		{
			get { return _isAnonymousType; }
		}

		/// <summary>
		/// Returns the collection of members associated with the <see cref="TypeMapping">TypeMapping</see>.
		/// </summary>
		public IMemberInfoCollection Members
		{
			get { return _members; }
		}

		/// <summary>
		/// Returns the collection of relations associated with the <see cref="TypeMapping">TypeMapping</see>.
		/// </summary>
		public IRelationInfoCollection Relations
		{
			get
			{
				// The relation mapping is created on demand. This is required, because the relation mapping
				// depents on created persistent mappings.
				if (_relations == null)
				{
					_relations = new RelationInfoCollection();
					MappingBuilder.CreateRelationMappings(_type, _relations);
				}
				return _relations;
			}
		}

		/// <summary>
		/// Returns the mapping for a given object.
		/// </summary>
		/// <param name="obj">The object the mapping is returned for.</param>
		public static TypeMapping GetTypeMapping(object obj)
		{
			return MappingContainer.GetTypeMapping(obj);
		}

		/// <summary>
		/// Returns the mapping for the given persistent type.
		/// </summary>
		/// <param name="type">Type of object the mapping is returned for.</param>
		public static TypeMapping GetTypeMapping(Type type)
		{
			return MappingContainer.GetTypeMapping(type);
		}

		/// <summary>
		/// Registers the given interface type with the persistent object type. This is required if an interface is registered 
		/// with a persistent type and the interface is used instead of the persistent type. The framework looks up the 
		/// registered interfaces to get the persistent object when loading objects from the storage.
		/// </summary>
		/// <param name="interfaceType">The string representation of the interface type registered with the persistent type.</param>
		/// <param name="persistentType">The string representation of the persistent type registered with the interface type.</param>
		/// <returns>True if the interface wasn't already registered.</returns>
		public static bool RegisterPersistentInterface(string interfaceType, string persistentType)
		{
			if (interfaceType == null)
				throw new ArgumentNullException("interfaceType");
			if (persistentType == null)
				throw new ArgumentNullException("persistentType");
			
			Type type1 = Type.GetType(interfaceType);
			if (type1 == null)
			{
				throw new ArgumentException(string.Format("Cannot create type from string: {0}. Please try Type.AssemblyQualifiedName if the type can't be created.", 
					interfaceType), "interfaceType");
			}
			Type type2 = Type.GetType(persistentType);
			if (type2 == null)
			{
				throw new ArgumentException(string.Format("Cannot create type from string: {0}. Please try Type.AssemblyQualifiedName if the type can't be created.", 
					persistentType), "persistentType");
			}
			return RegisterPersistentInterface(type1, type2);
		}

		/// <summary>
		/// Registers the given interface type with the persistent object type. This is required if an interface is registered 
		/// with a persistent type and the interface is used instead of the persistent type. The framework looks up the 
		/// registered interfaces to get the persistent object when loading objects from the storage.
		/// </summary>
		/// <param name="interfaceType">The type of the interface registered with the persistent type.</param>
		/// <param name="persistentType">The type of the persistent registered with the interface type.</param>
		/// <returns>True if the interface wasn't already registered.</returns>
		public static bool RegisterPersistentInterface(Type interfaceType, Type persistentType)
		{
			return MappingContainer.RegisterPersistentInterface(interfaceType, persistentType);
		}

		/// <summary>
		/// Gets the persistent type from the given type.
		/// </summary>
		/// <param name="type">The type that's persistent type is returned.</param>
		public static Type GetPersistentType(Type type)
		{
			return MappingContainer.GetPersistentType(type);
		}

		/// <summary>
		/// Gets the classes that implement the <see cref="ITypeMappingProvider">ITypeMappingProvider</see>
		/// interfaces. A class implementing the interface provides methods to create the mapping for a persistent type.
		/// </summary>
		public static TypeMappingProviderCollection Providers
		{
			get { return MappingBuilder.TypeMappingProviders; }
		}

		/// <summary>
		/// Gets or sets whether the framework should inject code in each persistent object to improve the performance
		/// of loading and saving of persistent objects. This can also be achieved by marking each persistent object that should
		/// get code injected with the <see cref="IDynamicPopulateHelper">IDynamicPopulateHelper</see> interface.
		/// </summary>
		public static bool AutoInjectDynamicPopulateHelper
		{
			get { return MappingBuilder.AutoInjectDynamicPopulateHelper; }
			set { MappingBuilder.AutoInjectDynamicPopulateHelper = value; }
		}

		/// <summary>
		/// Gets whether the given type is an anonymous type.
		/// </summary>
		/// <param name="type">The type that is inspected for being anonymous.</param>
		private static bool CheckIfAnonymousType(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			// HACK: The only way to detect anonymous types right now.
			return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)
				   && type.IsGenericType && type.Name.Contains("AnonymousType")
				   && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
				   && (type.Attributes & _nonPublic) == _nonPublic;
		}

		/// <summary>
		/// Gets whether the given type has a persistent mapping defined.
		/// </summary>
		/// <param name="type">The type that is checked for having a persistent mapping.</param>
		/// <returns>True, if a persistent mapping is found; otherwise false.</returns>
		public static bool HasPersistentMapping(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			// get the persistent type; we might deal with an interface.
			type = TypeMapping.GetPersistentType(type);

			// check if the persistent attribute is defined on the attribute.
			return Attribute.IsDefined(type, typeof(PersistentAttribute), true);
		}
	}
}
